# File containing functions used to apply filters to a set of data

import filterClass
from couchdb_util import viewGetter

import Queue
import threading
import copy
import json

def create_filters_fromlistDict(dictFilter, database):
    # create a dictionary list of filters from a list of dictionaries representing filters
    # returns a dictionary of list of filters divided by the type of data the filters works on
    filters = {}
    for dic in dictFilter:
        type = dic["type"]
        field = dic["key"]
        expr = dic["regexp"]
        filter = filterClass.Filter(database, type, field, expr)
        if type in filters:
            filters[type].append(filter)
        else:
            filters[type]=[filter];
    return filters

def divideFiltersByType(filters, database, couchdb_addr):
    # divided filters by their type and create threads
    # return the results of filtering divided by type 
    queue = Queue.Queue()
    commitThread = None
    emailThread = None
    issueThread = None
    if "commit" in filters:
        commitThread = TypeThread(filters["commit"], "commit", database, couchdb_addr, queue)
        commitThread.start()
        queue.put(commitThread)
    if "email" in filters:
        emailThread = TypeThread(filters["email"], "email", database, couchdb_addr, queue)
        emailThread.start()
        queue.put(emailThread)
    if "issue" in filters:
        issueThread = TypeThread(filters["issue"], "issue", database, couchdb_addr, queue)
        issueThread.start()
        queue.put(issueThread)
    queue.join()
    if commitThread:
        commits = commitThread.result
    else:
        commits = None
    if emailThread:
        emails = emailThread.result
    else:
        emails = None
    if issueThread:
        issues = issueThread.result
    else:
        issues = None
    return commits, emails, issues

######################################### Threads ##############################################

class TypeThread(threading.Thread):
    # A thread that is used to run a list of filters of the same type over a list of data 
    def __init__(self, listFilter, type, database, couchdb_addr, queue):
        threading.Thread.__init__(self)
        self.listFilters = listFilter
        self.type = type
        self.couchdb_addr = couchdb_addr
        self.database = database
        self.lock = threading.RLock()
        self.max_threads = 4
        self.queue = queue
        self.result = [] #list of result (ids of data)
        
    def run(self):
        print "Running filtering thread "+str(self.type)
        # get list of data
        self.datalist = []
        if self.type=="commit":
            self.datalist = viewGetter.getSmallCommitView(self.couchdb_addr, self.database)
        elif self.type=="email":
            self.datalist = viewGetter.getSmallEmailView(self.couchdb_addr, self.database)
        elif self.type=="issue":
            self.datalist = viewGetter.getIssueView(self.couchdb_addr, self.database)
        # create thread pool
        pool = Queue.Queue()
        threads = []
        copyfilters = copy.deepcopy(self.listFilters)
        if len(self.listFilters)<self.max_threads:
            self.max_threads = len(self.listFilters)
        for i in range(0, self.max_threads):
            threads.append(FilterThread(copyfilters, self.lock, self.datalist, i, pool))
            pool.put(threads[i])
            threads[i].start()
        pool.join() #wait until everything finish
        print "End filters "+str(self.type)
        # merge results -> only results in common for each thread
        if(len(threads)>1):
            for t in range(0, len(threads)):
                for r in threads[t].result:
                    insert = False
                    for p in range(t+1, len(threads)):
                        if not (r in threads[p].result):
                            insert=False
                            break
                        else:
                            insert=True
                    if insert:
                        self.result.append(r)
        else:
            self.result=threads[0].result
        self.queue.task_done()
        

class FilterThread(threading.Thread):
    # A thread used to run a specific filter over a list of data
    # where the list of data is intended to be a chunk of the original list
    def __init__(self, filterlist, lock, dataList, id, queue):
        threading.Thread.__init__(self)
        self.filterlist = filterlist
        self.lock = lock
        self.dataList = dataList
        self.id = id
        self.queue = queue
        self.result = [] #list of result (ids of data)
        
    def run(self):
        while not len(self.filterlist) == 0: 
            with self.lock:
                if len(self.filterlist)>0:
                    filter = self.filterlist.pop(0)
                    print str(self.id)+"- start filter: "+str(filter)
                else:
                    break
            if filter:
                tmpresult = []
                # TODO check in cache for result
                # apply the filter
                datas = json.loads(self.dataList)
                for data in datas:
                    #print "data "+str(data)
                    filt = filter.apply_to_data(data)
                    if filt:
                        if "_id" in data:
                            tmpresult.append(data["_id"])
                        elif "id" in data:
                            tmpresult.append(data["id"])
                        else: # this should never happen
                            print "Error occurred on data: "+str(data)
                print "end filter: "+str(filter)
                # TODO cache result of filter
                # merge result -> only common results
                if len(self.result)==0: 
                    self.result = tmpresult
                else:
                    newList = []
                    for r in self.result:
                        if (r in tmpresult):
                            newList.append(r)
                    self.result=newList
        self.queue.task_done()
                
#################################################################################################